// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.

#include <windows.h>
//#include <assert.h>
#include <vector>
#include <set>
#include <map>
#include <mutex>

using namespace std;
using namespace Platform;
using namespace Windows::Foundation;
using namespace Windows::System::Threading;

// Stored data for CREATE_SUSPENDED and ResumeThread.
struct PendingThreadInfo
{
	LPTHREAD_START_ROUTINE lpStartAddress;
	LPVOID lpParameter;
	HANDLE completionEvent;
	int nPriority;
};

struct WorkerInfo
{
	LPVOID lpAsyncAction;
	HANDLE completionEvent;;
};

static map<HANDLE, PendingThreadInfo> pendingThreads;
static map<HANDLE, WorkerInfo> workers;
static mutex pendingThreadsLock;


// Thread local storage.
typedef vector<void*> ThreadLocalData;

static __declspec(thread) ThreadLocalData* currentThreadData = nullptr;
static set<ThreadLocalData*> allThreadData;
static DWORD nextTlsIndex = 0;
static vector<DWORD> freeTlsIndices;
static mutex tlsAllocationLock;


// Converts a Win32 thread priority to WinRT format.
static WorkItemPriority GetWorkItemPriority(int nPriority)
{
	if (nPriority < 0)
		return WorkItemPriority::Low;
	else if (nPriority > 0)
		return WorkItemPriority::High;
	else
		return WorkItemPriority::Normal;
}

// Called at thread exit to clean up TLS allocations.
extern "C" void WINAPI TlsShutdownWP8()
{
	ThreadLocalData* threadData = currentThreadData;

	if (threadData)
	{
		{
			lock_guard<mutex> lock(tlsAllocationLock);

			allThreadData.erase(threadData);
		}

		currentThreadData = nullptr;

		delete threadData;
	}
}


// Helper shared between CreateThread and ResumeThread.
static void* StartThreadWP8(LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, HANDLE completionEvent, int nPriority, HANDLE threadHandle)
{
	auto workItemHandler = ref new WorkItemHandler([=](IAsyncAction^ operation)
	{
		// Run the user callback.
		try
		{
			lpStartAddress(lpParameter);
		}
		catch (...) { }

		//auto worker = workers.find(threadHandle);
		//if(worker == workers.end())
		//{
		// Clean up any TLS allocations made by this thread.
		TlsShutdownWP8();

		// Signal that the thread has completed.
		SetEvent(completionEvent);
		CloseHandle(completionEvent);
		//workers.erase(hThread);
		//}
		//else
		//workers.erase(threadHandle);

	}, CallbackContext::Any);

	IAsyncAction^ action = ThreadPool::RunAsync(workItemHandler, GetWorkItemPriority(nPriority), WorkItemOptions::TimeSliced);

	action->Completed = ref new AsyncActionCompletedHandler([](IAsyncAction^ action, AsyncStatus status)
	{
		if(status == AsyncStatus::Canceled)
			action->Close();
	});

	return (void*)action;
}


extern "C" _Use_decl_annotations_ HANDLE WINAPI CreateThreadWP8(LPSECURITY_ATTRIBUTES unusedThreadAttributes, SIZE_T unusedStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD unusedThreadId)
{
	// Validate parameters.
	/*assert(unusedThreadAttributes == nullptr);
	assert(unusedStackSize == 0);
	assert((dwCreationFlags & ~CREATE_SUSPENDED) == 0);
	assert(unusedThreadId == nullptr);*/

	// Create a handle that will be signalled when the thread has completed.
	HANDLE threadHandle = CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS);

	if (!threadHandle)
		return nullptr;

	// Make a copy of the handle for internal use. This is necessary because
	// the caller is responsible for closing the handle returned by CreateThread,
	// and they may do that before or after the thread has finished running.
	HANDLE completionEvent;

	if (!DuplicateHandle(GetCurrentProcess(), threadHandle, GetCurrentProcess(), &completionEvent, 0, false, DUPLICATE_SAME_ACCESS))
	{
		CloseHandle(threadHandle);
		return nullptr;
	}

	try
	{
		if (dwCreationFlags & CREATE_SUSPENDED)
		{
			// Store info about a suspended thread.
			PendingThreadInfo info;

			info.lpStartAddress = lpStartAddress;
			info.lpParameter = lpParameter;
			info.completionEvent = completionEvent;
			info.nPriority = 0;

			lock_guard<mutex> lock(pendingThreadsLock);

			pendingThreads[threadHandle] = info;
		}
		else
		{
			// Start the thread immediately.
			WorkerInfo winfo;
			winfo.lpAsyncAction = StartThreadWP8(lpStartAddress, lpParameter, completionEvent, 0, threadHandle);
			winfo.completionEvent = completionEvent;
			workers[threadHandle] = winfo;
		}

		return threadHandle;
	}
	catch (...)
	{
		// Clean up if thread creation fails.
		CloseHandle(threadHandle);
		CloseHandle(completionEvent);

		return nullptr;
	}
}


extern "C" _Use_decl_annotations_ DWORD WINAPI ResumeThreadWP8(HANDLE hThread)
{
	lock_guard<mutex> lock(pendingThreadsLock);

	// Look up the requested thread.
	auto threadInfo = pendingThreads.find(hThread);

	if (threadInfo == pendingThreads.end())
	{
		// Can only resume threads while they are in CREATE_SUSPENDED state.
		//assert(false);
		return (DWORD)-1;
	}

	// Start the thread.
	try
	{
		PendingThreadInfo& info = threadInfo->second;
		WorkerInfo winfo;
		winfo.lpAsyncAction = StartThreadWP8(info.lpStartAddress, info.lpParameter, info.completionEvent, info.nPriority, hThread);
		winfo.completionEvent = info.completionEvent;
		workers[hThread] = winfo;
	}
	catch (...)
	{
		return (DWORD)-1;
	}

	// Remove this thread from the pending list.
	pendingThreads.erase(threadInfo);

	return 0;
}

extern "C" _Use_decl_annotations_ DWORD WINAPI TerminateThreadWP8(HANDLE hThread, DWORD dwExitCode)
{
	(reinterpret_cast<IAsyncAction^>(workers[hThread].lpAsyncAction))->Cancel();
	//(reinterpret_cast<IAsyncAction^>(workers[hThread].lpAsyncAction))->Close();

	// Clean up any TLS allocations made by this thread.
	//TlsShutdownWP8();

	// Signal that the thread has completed.
	//SetEvent(workers[hThread].completionEvent);
	//CloseHandle(workers[hThread].completionEvent);
	//workers[hThread].completionEvent = NULL;

	workers.erase(hThread);

	return 0;
}

extern "C" _Use_decl_annotations_ BOOL WINAPI SetThreadPriorityWP8(HANDLE hThread, int nPriority)
{
	lock_guard<mutex> lock(pendingThreadsLock);

	// Look up the requested thread.
	auto threadInfo = pendingThreads.find(hThread);

	if (threadInfo == pendingThreads.end())
	{
		// Can only set priority on threads while they are in CREATE_SUSPENDED state.
		//assert(false);
		return false;
	}

	// Store the new priority.
	threadInfo->second.nPriority = nPriority;

	return true;
}



extern "C" DWORD WINAPI TlsAllocWP8()
{
	lock_guard<mutex> lock(tlsAllocationLock);

	// Can we reuse a previously freed TLS slot?
	if (!freeTlsIndices.empty())
	{
		DWORD result = freeTlsIndices.back();
		freeTlsIndices.pop_back();
		return result;
	}

	// Allocate a new TLS slot.
	return nextTlsIndex++;
}


extern "C" _Use_decl_annotations_ BOOL WINAPI TlsFreeWP8(DWORD dwTlsIndex)
{
	lock_guard<mutex> lock(tlsAllocationLock);

	//assert(dwTlsIndex < nextTlsIndex);
	//assert(find(freeTlsIndices.begin(), freeTlsIndices.end(), dwTlsIndex) == freeTlsIndices.end());

	// Store this slot for reuse by TlsAlloc.
	try
	{
		freeTlsIndices.push_back(dwTlsIndex);
	}
	catch (...)
	{
		return false;
	}

	// Zero the value for all threads that might be using this now freed slot.
	for each (auto threadData in allThreadData)
	{
		if (threadData->size() > dwTlsIndex)
		{
			threadData->at(dwTlsIndex) = nullptr;
		}
	}

	return true;
}


extern "C" _Use_decl_annotations_ LPVOID WINAPI TlsGetValueWP8(DWORD dwTlsIndex)
{
	ThreadLocalData* threadData = currentThreadData;

	if (threadData && threadData->size() > dwTlsIndex)
	{
		// Return the value of an allocated TLS slot.
		return threadData->at(dwTlsIndex);
	}
	else
	{
		// Default value for unallocated slots.
		return nullptr;
	}
}


extern "C" _Use_decl_annotations_ BOOL WINAPI TlsSetValueWP8(DWORD dwTlsIndex, LPVOID lpTlsValue)
{
	ThreadLocalData* threadData = currentThreadData;

	if (!threadData)
	{
		// First time allocation of TLS data for this thread.
		try
		{
			threadData = new ThreadLocalData(dwTlsIndex + 1, nullptr);

			lock_guard<mutex> lock(tlsAllocationLock);

			allThreadData.insert(threadData);

			currentThreadData = threadData;
		}
		catch (...)
		{
			if (threadData)
				delete threadData;

			return false;
		}
	}
	else if (threadData->size() <= dwTlsIndex)
	{
		// This thread already has a TLS data block, but it must be expanded to fit the specified slot.
		try
		{
			lock_guard<mutex> lock(tlsAllocationLock);

			threadData->resize(dwTlsIndex + 1, nullptr);
		}
		catch (...)
		{
			return false;
		}
	}

	// Store the new value for this slot.
	threadData->at(dwTlsIndex) = lpTlsValue;

	return true;
}

extern "C" _Use_decl_annotations_ VOID WINAPI SleepWP8(DWORD dwMilliseconds)
{
	static HANDLE singletonEvent = nullptr;

	HANDLE sleepEvent = singletonEvent;

	// Demand create the event.
	if (!sleepEvent)
	{
		sleepEvent = CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS);

		if (!sleepEvent)
			return;

		HANDLE previousEvent = InterlockedCompareExchangePointerRelease(&singletonEvent, sleepEvent, nullptr);

		if (previousEvent)
		{
			// Back out if multiple threads try to demand create at the same time.
			CloseHandle(sleepEvent);
			sleepEvent = previousEvent;
		}
	}

	// Emulate sleep by waiting with timeout on an event that is never signalled.
	WaitForSingleObjectEx(sleepEvent, dwMilliseconds, false);
}